/*
 ** $Id: ldblib.c,v 1.104.1.3 2008/01/21 13:11:21 roberto Exp $
 ** Interface from Lua to its debug API
 ** See Copyright Notice in lua.h
 */

using System;
using System.Collections.Generic;
using System.Text;

namespace KopiLua
{
    public partial class Lua
    {
        private static int db_getregistry (lua_State L) {
            lua_pushvalue(L, LUA_REGISTRYINDEX);
            return 1;
        }


        private static int db_getmetatable (lua_State L) {
            luaL_checkany(L, 1);
            if (lua_getmetatable(L, 1) == 0) {
                lua_pushnil(L);  /* no metatable */
            }
            return 1;
        }


        private static int db_setmetatable (lua_State L) {
            int t = lua_type(L, 2);
            luaL_argcheck(L, t == LUA_TNIL || t == LUA_TTABLE, 2,
                          "nil or table expected");
            lua_settop(L, 2);
            lua_pushboolean(L, lua_setmetatable(L, 1));
            return 1;
        }


        private static int db_getfenv (lua_State L) {
            luaL_checkany(L, 1);
            lua_getfenv(L, 1);
            return 1;
        }


        private static int db_setfenv (lua_State L) {
            luaL_checktype(L, 2, LUA_TTABLE);
            lua_settop(L, 2);
            if (lua_setfenv(L, 1) == 0)
                luaL_error(L, LUA_QL("setfenv") +
                           " cannot change environment of given object");
            return 1;
        }


        private static void settabss (lua_State L, CharPtr i, CharPtr v) {
            lua_pushstring(L, v);
            lua_setfield(L, -2, i);
        }


        private static void settabsi (lua_State L, CharPtr i, int v) {
            lua_pushinteger(L, v);
            lua_setfield(L, -2, i);
        }


        private static lua_State getthread (lua_State L, out int arg) {
            if (lua_isthread(L, 1)) {
                arg = 1;
                return lua_tothread(L, 1);
            }
            else {
                arg = 0;
                return L;
            }
        }


        private static void treatstackoption (lua_State L, lua_State L1, CharPtr fname) {
            if (L == L1) {
                lua_pushvalue(L, -2);
                lua_remove(L, -3);
            }
            else
                lua_xmove(L1, L, 1);
            lua_setfield(L, -2, fname);
        }


        private static int db_getinfo (lua_State L) {
            lua_Debug ar = new lua_Debug();
            int arg;
            lua_State L1 = getthread(L, out arg);
            CharPtr options = luaL_optstring(L, arg+2, "flnSu");
            if (lua_isnumber(L, arg+1) != 0) {
                if (lua_getstack(L1, (int)lua_tointeger(L, arg+1), ar)==0) {
                    lua_pushnil(L);  /* level out of range */
                    return 1;
                }
            }
            else if (lua_isfunction(L, arg+1)) {
                lua_pushfstring(L, ">%s", options);
                options = lua_tostring(L, -1);
                lua_pushvalue(L, arg+1);
                lua_xmove(L, L1, 1);
            }
            else
                return luaL_argerror(L, arg+1, "function or level expected");
            if (lua_getinfo(L1, options, ar)==0)
                return luaL_argerror(L, arg+2, "invalid option");
            lua_createtable(L, 0, 2);
            if (strchr(options, 'S') != null) {
                settabss(L, "source", ar.source);
                settabss(L, "short_src", ar.short_src);
                settabsi(L, "linedefined", ar.linedefined);
                settabsi(L, "lastlinedefined", ar.lastlinedefined);
                settabss(L, "what", ar.what);
            }
            if (strchr(options, 'l') != null)
                settabsi(L, "currentline", ar.currentline);
            if (strchr(options, 'u')  != null)
                settabsi(L, "nups", ar.nups);
            if (strchr(options, 'n')  != null) {
                settabss(L, "name", ar.name);
                settabss(L, "namewhat", ar.namewhat);
            }
            if (strchr(options, 'L') != null)
                treatstackoption(L, L1, "activelines");
            if (strchr(options, 'f')  != null)
                treatstackoption(L, L1, "func");
            return 1;  /* return table */
        }
        

        private static int db_getlocal (lua_State L) {
            int arg;
            lua_State L1 = getthread(L, out arg);
            lua_Debug ar = new lua_Debug();
            CharPtr name;
            if (lua_getstack(L1, luaL_checkint(L, arg+1), ar)==0)  /* out of range? */
            return luaL_argerror(L, arg+1, "level out of range");
            name = lua_getlocal(L1, ar, luaL_checkint(L, arg+2));
            if (name != null) {
                lua_xmove(L1, L, 1);
                lua_pushstring(L, name);
                lua_pushvalue(L, -2);
                return 2;
            }
            else {
                lua_pushnil(L);
                return 1;
            }
        }


        private static int db_setlocal (lua_State L) {
            int arg;
            lua_State L1 = getthread(L, out arg);
            lua_Debug ar = new lua_Debug();
            if (lua_getstack(L1, luaL_checkint(L, arg+1), ar)==0)  /* out of range? */
            return luaL_argerror(L, arg+1, "level out of range");
            luaL_checkany(L, arg+3);
            lua_settop(L, arg+3);
            lua_xmove(L, L1, 1);
            lua_pushstring(L, lua_setlocal(L1, ar, luaL_checkint(L, arg+2)));
            return 1;
        }


        private static int auxupvalue (lua_State L, int get) {
            CharPtr name;
            int n = luaL_checkint(L, 2);
            luaL_checktype(L, 1, LUA_TFUNCTION);
            if (lua_iscfunction(L, 1)) return 0;  /* cannot touch C upvalues from Lua */
            name = (get!=0) ? lua_getupvalue(L, 1, n) : lua_setupvalue(L, 1, n);
            if (name == null) return 0;
            lua_pushstring(L, name);
            lua_insert(L, -(get+1));
            return get + 1;
        }


        private static int db_getupvalue (lua_State L) {
            return auxupvalue(L, 1);
        }


        private static int db_setupvalue (lua_State L) {
            luaL_checkany(L, 3);
            return auxupvalue(L, 0);
        }



        private const string KEY_HOOK = "h";


        private static readonly string[] hooknames =
        {"call", "return", "line", "count", "tail return"};

        private static void hookf (lua_State L, lua_Debug ar) {
            lua_pushlightuserdata(L, KEY_HOOK);
            lua_rawget(L, LUA_REGISTRYINDEX);
            lua_pushlightuserdata(L, L);
            lua_rawget(L, -2);
            if (lua_isfunction(L, -1)) {
                lua_pushstring(L, hooknames[(int)ar.event_]);
                if (ar.currentline >= 0)
                    lua_pushinteger(L, ar.currentline);
                else lua_pushnil(L);
                lua_assert(lua_getinfo(L, "lS", ar));
                lua_call(L, 2, 0);
            }
        }


        private static int makemask (CharPtr smask, int count) {
            int mask = 0;
            if (strchr(smask, 'c') != null) mask |= LUA_MASKCALL;
            if (strchr(smask, 'r') != null) mask |= LUA_MASKRET;
            if (strchr(smask, 'l') != null) mask |= LUA_MASKLINE;
            if (count > 0) mask |= LUA_MASKCOUNT;
            return mask;
        }


        private static CharPtr unmakemask (int mask, CharPtr smask) {
            int i = 0;
            if ((mask & LUA_MASKCALL) != 0) smask[i++] = 'c';
            if ((mask & LUA_MASKRET) != 0) smask[i++] = 'r';
            if ((mask & LUA_MASKLINE) != 0) smask[i++] = 'l';
            smask[i] = '\0';
            return smask;
        }


        private static void gethooktable (lua_State L) {
            lua_pushlightuserdata(L, KEY_HOOK);
            lua_rawget(L, LUA_REGISTRYINDEX);
            if (!lua_istable(L, -1)) {
                lua_pop(L, 1);
                lua_createtable(L, 0, 1);
                lua_pushlightuserdata(L, KEY_HOOK);
                lua_pushvalue(L, -2);
                lua_rawset(L, LUA_REGISTRYINDEX);
            }
        }


        private static int db_sethook (lua_State L) {
            int arg, mask, count;
            lua_Hook func;
            lua_State L1 = getthread(L, out arg);
            if (lua_isnoneornil(L, arg+1)) {
                lua_settop(L, arg+1);
                func = null; mask = 0; count = 0;  /* turn off hooks */
            }
            else {
                CharPtr smask = luaL_checkstring(L, arg+2);
                luaL_checktype(L, arg+1, LUA_TFUNCTION);
                count = luaL_optint(L, arg+3, 0);
                func = hookf; mask = makemask(smask, count);
            }
            gethooktable(L);
            lua_pushlightuserdata(L, L1);
            lua_pushvalue(L, arg+1);
            lua_rawset(L, -3);  /* set new hook */
            lua_pop(L, 1);  /* remove hook table */
            lua_sethook(L1, func, mask, count);  /* set hooks */
            return 0;
        }


        private static int db_gethook (lua_State L) {
            int arg;
            lua_State L1 = getthread(L, out arg);
            CharPtr buff = new char[5];
            int mask = lua_gethookmask(L1);
            lua_Hook hook = lua_gethook(L1);
            if (hook != null && hook != hookf)  /* external hook? */
            lua_pushliteral(L, "external hook");
            else {
                gethooktable(L);
                lua_pushlightuserdata(L, L1);
                lua_rawget(L, -2);   /* get hook */
                lua_remove(L, -2);  /* remove hook table */
            }
            lua_pushstring(L, unmakemask(mask, buff));
            lua_pushinteger(L, lua_gethookcount(L1));
            return 3;
        }


        private static int db_debug (lua_State L) {
            for (;;) {
                CharPtr buffer = new char[250];
                fputs("lua_debug> ", stderr);
                if (fgets(buffer, stdin) == null ||
                    strcmp(buffer, "cont\n") == 0)
                    return 0;
                if (luaL_loadbuffer(L, buffer, (uint)strlen(buffer), "=(debug command)")!=0 ||
                    lua_pcall(L, 0, 0, 0)!=0) {
                    fputs(lua_tostring(L, -1), stderr);
                    fputs("\n", stderr);
                }
                lua_settop(L, 0);  /* remove eventual returns */
            }
        }


        public const int LEVELS1	= 12;	/* size of the first part of the stack */
        public const int LEVELS2	= 10;	/* size of the second part of the stack */

        private static int db_errorfb (lua_State L) {
            int level;
            bool firstpart = true;  /* still before eventual `...' */
            int arg;
            lua_State L1 = getthread(L, out arg);
            lua_Debug ar = new lua_Debug();
            if (lua_isnumber(L, arg+2) != 0) {
                level = (int)lua_tointeger(L, arg+2);
                lua_pop(L, 1);
            }
            else
                level = (L == L1) ? 1 : 0;  /* level 0 may be this own function */
            if (lua_gettop(L) == arg)
                lua_pushliteral(L, "");
            else if (lua_isstring(L, arg+1)==0) return 1;  /* message is not a string */
            else lua_pushliteral(L, "\n");
            lua_pushliteral(L, "stack traceback:");
            while (lua_getstack(L1, level++, ar) != 0) {
                if (level > LEVELS1 && firstpart) {
                    /* no more than `LEVELS2' more levels? */
                    if (lua_getstack(L1, level+LEVELS2, ar)==0)
                        level--;  /* keep going */
                    else {
                        lua_pushliteral(L, "\n\t...");  /* too many levels */
                        while (lua_getstack(L1, level+LEVELS2, ar) != 0)  /* find last levels */
                        level++;
                    }
                    firstpart = false;
                    continue;
                }
                lua_pushliteral(L, "\n\t");
                lua_getinfo(L1, "Snl", ar);
                lua_pushfstring(L, "%s:", ar.short_src);
                if (ar.currentline > 0)
                    lua_pushfstring(L, "%d:", ar.currentline);
                if (ar.namewhat != '\0')  /* is there a name? */
                lua_pushfstring(L, " in function " + LUA_QS, ar.name);
                else {
                    if (ar.what == 'm')  /* main? */
                    lua_pushfstring(L, " in main chunk");
                    else if (ar.what == 'C' || ar.what == 't')
                        lua_pushliteral(L, " ?");  /* C function or tail call */
                    else
                        lua_pushfstring(L, " in function <%s:%d>",
                                        ar.short_src, ar.linedefined);
                }
                lua_concat(L, lua_gettop(L) - arg);
            }
            lua_concat(L, lua_gettop(L) - arg);
            return 1;
        }


        private readonly static luaL_Reg[] dblib = {
            new luaL_Reg("debug", db_debug),
            new luaL_Reg("getfenv", db_getfenv),
            new luaL_Reg("gethook", db_gethook),
            new luaL_Reg("getinfo", db_getinfo),
            new luaL_Reg("getlocal", db_getlocal),
            new luaL_Reg("getregistry", db_getregistry),
            new luaL_Reg("getmetatable", db_getmetatable),
            new luaL_Reg("getupvalue", db_getupvalue),
            new luaL_Reg("setfenv", db_setfenv),
            new luaL_Reg("sethook", db_sethook),
            new luaL_Reg("setlocal", db_setlocal),
            new luaL_Reg("setmetatable", db_setmetatable),
            new luaL_Reg("setupvalue", db_setupvalue),
            new luaL_Reg("traceback", db_errorfb),
            new luaL_Reg(null, null)
        };


        public static int luaopen_debug (lua_State L) {
            luaL_register(L, LUA_DBLIBNAME, dblib);
            return 1;
        }

    }
}
